# Clue inference engine.

class State:
  """ Represents the sum of knowledge we have about a game. """
  def __init__(self, players, cards):
    self.players = set(players)
    self.cards = set(cards)
    # Keys are cards, values are sets of players.
    self.lack = {}
    for card in self.cards:
      self.lack[card] = set([])
    # Keys are cards, values are a player. If we don't know who has a card, that
    # card is not present in this dict.
    self.have = {}
    # List of tuples. Each tuple is a (player, set of cards).
    self.haveOneOf = set([])

  def add(self, other):
    """ Combines two States together into 'self'. """
    for card in other.lack.keys():
      self.lack[card] |= other.lack[card]
    # TODO: throw an error if the have sets are inconsistent. Also consider
    # throwing an error of the have and lack sets of the combined State are
    # inconsistent.
    self.have = dict(self.have.items() + self.have.items())
    self.haveOneOf |= other.haveOneOf

  # TODO: Add a method to check whether one State is a subset of another State.
  # We should verify that, after each inference, the new State is a superset
  # of the old State.
  def equals(self, other):
    return (self.lack == other.lack and self.have == other.have and
            self.haveOneOf == other.haveOneOf)

  def inferLackFromHave(self):
    """ If someone has a card, set the lack bit for everyone else. """
    for card in self.have:
      # If you have the card, everyone else lacks it.
      self.lack[card] = self.players - set([self.have[card]])
      
  def inferHaveFromLack(self):
    """ If a card has all but one lack bit set, set the have bit. """
    pass # TODO:

  def inferFromHandSize(self):
    """ If the number of lack-bits for a player is >= (the total number of cards
    - 2 - the size of the player's hand), add have-one-ofsets for this player
    for all sets of 3 cards which are not lacked by this player. """
    pass # TODO:

  def inferHaveOneOfCollapse(self):
    """ If a lack-bit is set for a card in one of the player's have-one-of-sets,
    reduce the size of the have-one-of-set by one. If the set's size is reduced 
    to one, remove it and add a have bit. """
    pass # TODO:

  def inferHaveOneOfClosure(self):
    """ If we can find a subset of the have-one-of tuples for which the total
    number of cards mentioned is equal to the total number of players mentioned,
    we may set the lack-bits for the mentioned cards and the *un*mentioned
    players."""
    # TODO: we may be able to generalize this to also include disjoint
    # have-one-of-sets for the same player.
    pass # TODO:

  def inferCardTypesInEnvelope(self):
    """ We know that the envelope has exactly one card of each type. If we have 
    a have-bit for the envelope, we can set lack-bits for the envelope for other
    cards of that same type. """
    pass # TODO:
